<?php
/**
 * @author Mcfly
 * @version 1.0
 * Данный класс управляет заказами.
 * 
 * Таблица с: pref_report_list
 * 
 */
class OrderList extends A_Content 
{
	const 
		_xml_config = "order_list.xml";
	private 
		$_flag_index,			// список флагов заказа. логическая маска для битового поля pref_order_list.flags
		$_flag_defaults,		// список флагов и значений по умолчанию 
		$_flag_names,           //список человеских названий флагов
		$_catalog_id;			// указатель на каталог с которым работает класс заказов
		
    //todo: оставить на будущее, когда появятся сложные товары
	public $_profile_class_name;	// имя класса профайла
    	
	public $catalog;
	
    /**
     * Returns catalog class
     *
     * @return SingleCatalog
     */
    public function getFlagNames(){
        return $this->_flag_names;
    }

    public function getFlagBits(){
        return $this->_flag_index;
    }

	public function __construct($top,$parent)
	{		
		parent::__construct($top,$parent);
		
		unset($this->catalog);
		
		$xmlDoc = $this->_top->Utils->ParseXML($this->_top->_config->general->config_path.self::_xml_config);
		// Заполняем массив с флагами
		$flagNodes = $xmlDoc->getElementsByTagName("flag");
		if ($flagNodes!=null) {
			$totalFlags = $flagNodes->getLength();
			for ($f=0;$f<$totalFlags;$f++) {
				$flagNode = $flagNodes->item($f);
				$this->_flag_index[$flagNode->getAttribute('key')] = $flagNode->getAttribute('bit');
				$this->_flag_defaults[$flagNode->getAttribute('key')] = $flagNode->getAttribute('default');
				$this->_flag_names[$flagNode->getAttribute('key')] = $flagNode->getAttribute('name');
			}
		}

		// Читаем данные о файле с классом профайла
		$profoleNodes = $xmlDoc->getElementsByTagName("profile");
		if ($profoleNodes!=null) {
			$profoleNode = $profoleNodes->item(0);
			$this->_profile_class_name = $profoleNode->getAttribute('className');
			$profile_class_path = $profoleNode->getAttribute('classPath');
			if (!file_exists($profile_class_path)) {
				throw $this->_top->Utils->PushError("Can't find file with profile class $profile_class_path", "Не найден класс с файлом профайла");
			} else {
				require_once $profile_class_path;
			}
		} else {
			throw $this->_top->Utils->PushError("Can't find info about profile class in ini file", "Не найдена информация о классе профайла в настроечном файле");
		}
		
		// читаем id каталога
		$catalogNodes = $xmlDoc->getElementsByTagName('catalog');
		if ($catalogNodes!=null) {
			$this->_catalog_id = $catalogNodes->item(0)->getAttribute('id');
		} else {
			throw $this->_top->Utils->PushError("Can't find info about catalog in ini file", "Не найдена информация в настроечном файле о каталоге, с которым работает система заказа");
		}
		return true;
	}
	
	
	public function __get($param)
	{
		if( $param == '_flag_defaults' ) {
		    return $this->_flag_defaults;
		} elseif ($param=='_catalog_id') {
			return $this->_catalog_id;
		} elseif ($param=='catalog') {
			return MF_Main::getInstance()->Catalog->getById($this->_catalog_id);
		}
        return false;
	}
	
	
	/**
	 * 	@deprecated архаизм который прикрутил один нерадивы программист вместо того чтобы по человечески сделать корзину 
	 * 
	 * Получить текущий, формируемый заказ (flag == 0)
	 * Он же "Корзина"
	 *
	 */
	public function getCurrent($user_id, $createIfNeed = true){
        $orders = $this->_top->_sql->exe("SELECT order_id FROM ".TAB_PREF."order_list WHERE user_id=$user_id and flags=0");
        if( count($orders) ){//заказ есть
            return $this->getById($orders[0]['order_id']);
        }else{
            if( $createIfNeed ){//надо создать
                return $this->addOrder($user_id);
            }
        }
        return 0;
	}	
	
	/**
	 * Выбирает заказ по его базовому параметру order_id
	 * 
	 * @param int $id
	 * @return User object
	 */
	public function getById($id)
	{
		if (!isset($this->_cashe_obj[$id])) {
			$this->_cashe_obj[$id] = new Order($this->_top,$this,$id);
		}
		
		return $this->_cashe_obj[$id];
	}
	
	public function add()
	{
		return false;
	}
	
	
	/**
	 * Метод создает новый заказ для пользователя и возвращет его(заказа) идешник
	 * 
	 * @return User object
	 **/
	public function addOrder($user_id)
	{
        $users = MF_Main::getInstance()->UserList;
		$check_login = $users->UserSearch();
		$check_login->user_id = $user_id;
		$check_login->Make();

		//todo:Проверить, есть ли у пользователя незаконченные заказы
		$curr = $this->getCurrent($user_id, false);
		if( $curr <> 0 ){//есть незаконченные заказы
            throw $this->_top->Utils->PushError("Unfinished order is exists", "У данного пользователя уже есть корзина");
		}		

		if ($check_login->length() == 0) {
			throw $this->_top->Utils->PushError("User with such Login not foune","Пользователя с таким логином не существует");
		} else {
//			$number = '111';//todo: сгенерить уникальный номер заказа (например - год.месяц.год.кол-во за сегодня)
			$this->_top->_sql->exeDummy("INSERT INTO ".TAB_PREF."order_list(user_id,date_time) VALUES('$user_id',NOW() )");
			$new_order = $this->_top->_sql->last_insert_id();
			
			return $this->getById($new_order);
		}
	}
	
	
	/**
	 * Возвращает класс OrderSearch
	 *
	 * @return UserSearch object
	 */
	public function OrderSearch()
	{
		return new OrderSearch($this->_top, $this);
	}	
	
	public function getFlagIndex($flag_name)
	{
		if (!isset($this->_flag_index[$flag_name])) throw $this->_top->Utils->PushError("No such user flag","Попытка обращения к несуществующему флагу заказа");
		return $this->_flag_index[$flag_name];
	}
	
	//расшифровывает флаги в виде строк, разделенных $aSeparator
	public function getCurrentFlags($aOrder, $aSeparator){
//	   return Zend_Debug::dump($this->_flag_names, null, false);
        $val = $aOrder->flag->value;
        $res = "";

        foreach($this->_flag_index as $sysname => $i){
            if( $this->_top->BitMask->bitValue($val, $i) ){ //есть такой флаг
                if( $res ) //разделитель
                    $res .= $aSeparator;            
                $res .= $this->_flag_names[$sysname];
            }
        }
        
        return $res;
	}
}




/**
 * Класс производящий поиск по таблице заказов
 * 
 * Пример использования: 
 * $search = new OrderSearch(MF_Main::getInstance);
 * $search->user_id = 1;
 * $search->flag->is_vip = 1;
 * $res = $search->Make();
 *
 */
class OrderSearch
{
	private 
		$_top, $search_params = array(), $found = false, $_total_amount=null, $_order_list, $_limits=null;
	
	function __construct($top, $order_list)
	{
		$this->_top = $top;
		$this->_order_list = $order_list;
	}
	
	
	function __get($param)
	{
		if ($param=="flag") return new OrderSearchFlag($this->_top, $this);
	}

	public function setLimits($from_row,$amount) {
		// TODO - need to make validation here
		$this->_limits = array($from_row,$amount);
		return $this;
	}	

	public function __set($param,$value)
	{
        $pref = "a.";
        if( strchr($param, "." ) )
            $pref = "";
        $value = $this->_top->Utils->prepareValueForSQL($value);
        $c = 0;
        $value_r = str_replace('*', '%', $value);
        $c = ($value_r!=$value)? " LIKE ":"="; //если были замены, то знак = превращаем в LIKE
		array_push($this->search_params," AND ".$pref."$param".$c."'$value_r'");
	}

	public function dateFromTo($from,$to) {
		// TODO - need to make validation here
		array_push($this->search_params, " AND a.date_time > '{$from} 00:00:00' AND  a.date_time < '{$to} 23:59:59'");
		return $this;
	}
	
	/**
	 * метод добавляет в условия поиска 
	 *
	 * @param string $flag_name - название флага
	 * @param int $value - значение флага, 1 или 0
	 */
	public function addFlagCondition($flag_name,$value)
	{
        if( $flag_name == 'value' ){
            array_push($this->search_params," AND a.flags $value ");
        } else {
            $mask = $this->_top->BitMask->makeBitMask($this->_order_list->getFlagIndex($flag_name));
            $sign = $value==1 ? "=" : "<>";
            array_push($this->search_params," AND (a.flags &  $mask) $sign $mask");
        }
	}

	/**
	 * Собирает все что указано в качестве аргументов поиска 
	 * @return array массив объектов User найденных пользователей 
	 */
	public function Make()
	{
        //считаем общее кол-во (без лимитов)
        $get_amount = $this->_top->_sql->exe("SELECT count(*) as c ".
            "FROM ".TAB_PREF."order_list a, ".TAB_PREF."user_list b WHERE 1 and a.user_id = b.user_id ".implode(" ",$this->search_params));
        $this->_total_amount = $get_amount[0]['c'];

		$this->found = array();

		$orders = $this->_top->_sql->exe("SELECT a.order_id ".
		  "FROM ".TAB_PREF."order_list a, ".TAB_PREF."user_list b WHERE 1 and a.user_id = b.user_id".implode(" ",$this->search_params).
		  " order by date_time desc".($this->_limits!==null ? " LIMIT {$this->_limits[0]},{$this->_limits[1]}" : ''));
		for ($u=0;$u<count($orders);$u++)
			array_push($this->found, $orders[$u]['order_id']);

		return true;
	}
	
	public function MakeForUserLogin($login){
        $value = str_replace('*', '%', $value);
	}
	
	/**
	 * Возвращает количество найденных заказов
	 * @return int
	 */
	public function length()
	{
		if (!$this->found) return false;
		return count($this->found);
	}
	
	public function total_length() {
//	   if (!$this->found) return false;
		return $this->_total_amount;
	}		
	
	/**
	 * Возвращаяет заказы по индексу из массива найденных заказов
	 * @param int $idx - индекс заказа в списке найденных заказов
	 * @return User
	 */
	public function item($idx)
	{
		if (!$this->found) return false;
		return $this->_top->OrderList->getById($this->found[$idx]);
	}
}

/**
 * Класс организующий поиск по флагам заказа (битовая маска)
 *
 */
class OrderSearchFlag {
	private $_top;
	/**
	 * Ссылка на дочерний клсс UserSearch
	 *
	 * @var UserSearch
	 */
	private $_order_search;

	function __construct($top,$order_search) {
		$this->_top = $top;
		$this->_order_search = $order_search;
	}

	public function __set($param,$value)
	{
		$this->_order_search->addFlagCondition($param,$value);
	}
}

class Order extends A_SingleContent 
{
// пока не надо, но потом может понадобится
	private $_profile_class;
    protected  $_goods = array();
    protected $_cashe_good=array();

    /**
     * Return user object
     *
     * @var User
     */
    public $user;
    
	protected function loader()
	{
		unset($this->user);
		
		
		$order_data = $this->_top->_sql->exe("SELECT * FROM ".TAB_PREF."order_list WHERE order_id={$this->_content_id}");
		if (count($order_data)==0) {			   
			throw $this->_top->Utils->PushError("Order NOT FOUND","Заказ по указанному order_id не найден");
		} else {
		
			$this->_data = $order_data[0];
			//загружаем товары с привязкой к справочнику
			$goods = $this->_top->_sql->exe("SELECT `good_id` FROM ".TAB_PREF."order_good  WHERE order_id=$this->_content_id");
			$this->_goods = array();
			for ($i=0; $i < count($goods); $i++){
                array_push($this->_goods, $goods[$i]['good_id']);
			}
			
            $profile_class = $this->_top->OrderList->_profile_class_name;
            $this->_profile_class = new $profile_class($this->_top, $this, $this->_content_id);
		}
	}
	
	/**
	 * Enter description here...
	 *
	 */
	public function length(){
	   return count($this->_goods);
	}
	
	/**
	 * Возвратит ключ первого выставленного флага
	 *
	 */
	public function flagAsKey(){
	   foreach($this->_parent->getFlagNames() as $key => $f){
	       if( $this->flag->$key )
	           return $key;
	   }
	   return 0;
	}

	public function flagAsString($aSeparate){
	   return $this->_parent->getCurrentFlags($this, $aSeparate);
	}
	
	public function item($index){
	   //todo: приделать абстракцию
	   return $this->getById($this->_goods[$index]); 
//        return new OrderProfile($index);
	}

	/**
	 * возвращает экзепляр класса Good
	 *
	 * @param unknown_type $id
	 * @return unknown
	 */
	public function getById($id){
		if( !isset($this->_cashe_good[$id]) ) {
			$this->_cashe_good[$id] = new Good($this->_top, $this, $id);
		}
		return $this->_cashe_good[$id];
	}
	
	/**
	 * Положить в заказ товар
	 *
	 * @param unknown_type $item_id - идешник товара
	 * @param unknown_type $count - кол-во товара
	 */
	public function add($item_id, $name, $count, $price){
        //проверить, чтобы товар не был собранным, оплаченным, отправленным или заблокированным
        $count = $this->_top->Utils->prepareValueForSQL($count);
        $name = $this->_top->Utils->prepareValueForSQL($name);
        //запоминаем там цену и сумму
//        $price = $this->_top->_sql->exe("select price from ".TAB_PREF."catalog_item where item_id=$item_id");
//        $price = $price[0]['price'];
        $summa = $price*$count;
        
        $item_id = trim($item_id)=='' ? 0 : $item_id;
        
        $this->_top->_sql->exeDummy("INSERT INTO ".TAB_PREF."order_good(order_id, name, item_id, count, price, summa) 
        	VALUES($this->_content_id, '$name', $item_id, '$count', '$price', '$summa')");
        
        $this->loader();
	}	
	
	public function deleteItem($item_id){//удалить из заказа товар
        $this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."order_good where order_id=$this->_content_id and item_id=$item_id");
        $this->recalc();//пересчитали
	}

	public function Clear(){//очистить заказ (нужно для очистки корзины)
        $this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."order_good where order_id=$this->_content_id");		
	}
	
	/**
	 * возвращает true, если этот товар есть в заказе
	 *
	 * @param unknown_type $item_id
	 */
	public function isExist($item_id){
        $res = $this->_top->_sql->exe("select count(*) as c from ".TAB_PREF."order_good where order_id=$this->_content_id and item_id=$item_id");
        return ($res[0]['c'])? true: false;
	}

	/**
	 * Получить кол-во какого-то товара в заказе
	 *
	 * @param unknown_type $item_id
	 */
	public function count(){
		  $res = $this->_top->_sql->exe("select count(*) as c from ".TAB_PREF."order_good where order_id=$this->_content_id");
		  return $res[0]['c'];
	}

	//возвращает сумму всех товаров (количество)
	public function countTotal(){
		  $res = $this->_top->_sql->exe("select sum(count) as c from ".TAB_PREF."order_good where order_id=$this->_content_id");
		  return $res[0]['c'];
	}
	
	public function changeCount($item_id, $new_count){//изменить кол-во товара
        if( $new_count == 0 ) //изменить на ноль, т.е. удалить
            $this->deleteItem($item_id);
        else{
            //изменяем счетчик пересчитываем сумму            
            $good = $this->itemById($item_id);
//            echo $good->price;
            $good->count = $new_count;
            echo $new_count * $good->price."<br />";
            $good->summa = $new_count * $good->price;
/*            $cat = MF_Main::getInstance()->Catalog->getById(1);
            
            $price = $cat->getItemById($item_id)->price;
            $summ = $price*$new_count;
//            echo "UPDATE ".TAB_PREF."order_good SET count=$new_count where order_id=$this->_content_id and item_id=$item_id<br />";
            $this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."order_good SET count=$new_count, price=$price, summa=$summ where order_id=$this->_content_id and item_id=$item_id");		
*/
            //ищем в массиве $_goods и там тоже меняем
            for( $i=0; $i < count($this->_goods); $i++ ){
                if( $this->_goods[$i]['item_id'] == $item_id ){
                    $this->_goods[$i]['count'] = $new_count;
                }
            }
        }
	}

	public function __get($param)
	{
		if ($param=="flag") { 
			return new OrderFlags($this->_top,$this);
		} elseif ($param=='user'){
			return $this->_top->UserList->getById($this->user_id);
		} else if( $param == 'flag_defaults' ){
            return $this->_parent->_flag_defaults;
		} elseif (!isset($this->_data[$param])) { 
			return $this->_profile_class->$param;
		} else {
			return $this->_data[$param];
		}
	}	

	public function __set($param,$value)
	{
		if($param=="order_id") {
			return false;
		} else {
			$value = $this->_top->Utils->prepareValueForSQL($value);
			
			$this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."order_list SET $param='$value' WHERE order_id={$this->_content_id}");
			$this->_data[$param] = $value;
			return true;
		}
	}

	/**
	 * записывает в date_time текущую дату
	 * __set не получилось использовать из-за кавычек вокруг '$value'
	 *
	 */
	public function FixDateTime(){
        $this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."order_list SET date_time=NOW() WHERE order_id={$this->_content_id}");
	}	

	protected function deleter()
	{
		$this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."order_list WHERE order_id={$this->_content_id}");
		//удаляем все товары из данного заказа
		$this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."order_items WHERE order_id={$this->_content_id}");
//		$this->_profile_class->delete();
	}
	
	/**
	 * пересчитываем общую сумму товара
	 *
	 */
	public function recalc(){
//	   $this->loader();
        $summa = 0;
        //берем список всех товаров в корзине 
        for($i=0; $i < $this->count(); $i++){
            $item = $this->item($i);
            $item['summa'] = $item['price']*$item['count'];
            $summa += $item['summa'];
        }        
        $this->summa = $summa;
	}    
}


/**
 * класс товара, положенного в корзину
 *
 */
class Good extends A_SingleContent {

//		$this->_top - это ссылка на экземпляр списка заказов
//		$this->_parent =& $parent - ссылка на экземпляр заказа, в котором находится данный товар
//		$this->_content_id = $id - это идентификатор товара
		
    protected function loader(){
		$item_data = $this->_top->_sql->exe("SELECT item_id, name, price, count, summa FROM ".TAB_PREF."order_good"
            ." WHERE good_id={$this->_content_id}");        
		if (count($item_data)==0) {
			throw $this->_top->Utils->PushError("Item NOT FOUND","Товар по указанному item_id не найден");
		} else {
			$this->_data = $item_data[0];
        
		//загружаем товары с привязкой к справочнику
//			$goods = $this->_top->_sql->exe("SELECT a.*, b.* FROM ".TAB_PREF."order_good a, ".TAB_PREF."catalog_item b WHERE a.item_id = b.item_id and order_id=$this->_content_id");
//			for ($i=0; $i < count($goods); $i++)
//                array_push($this->_goods, $goods[$i]);
//			$profile_class = $this->_top->OrderList->_profile_class_name;
//			$this->_profile_class = new $profile_class($this->_top,$this,$this->_content_id);
		}
	}
	
	public function __get($param)
	{
//		} elseif (!isset($this->_data[$param])) { 
//			return $this->_profile_class->$param;
//		} else {
		return $this->_data[$param];
//		}
	}

	public function __set($param,$value)
	{
//		if (!isset($this->_data[$param])) {
//			$this->_profile_class->$param = $value;
//		}else{
			$value = $this->_top->Utils->prepareValueForSQL($value);
				
			$this->_top->_sql->exeDummy("UPDATE ".TAB_PREF."order_good SET $param='$value' ".
                "WHERE item_id=$this->_content_id AND order_id=".$this->_parent->order_id);
			$this->_data[$param] = $value;
			return true;
//		}
	}

	//удалить товар из заказа
	protected function deleter()
	{
//		$this->_top->_sql->exeDummy("DELETE FROM ".TAB_PREF."user_list WHERE user_id={$this->_content_id}");
//		$this->_profile_class->delete();
	}	
	
}

class OrderFlags
{
	private 
		$_top,
		$_parent;

	public function __construct($top,$parent)
	{
		$this->_top = $top;
		$this->_parent = $parent; //указывает на конкретный заказ
	}
	
	public function __get($param)
	{
        if( $param == 'value' ){ //надо получить просто значение флага "как есть"
            return $this->_parent->flags;
        }else{
            return $this->_top->BitMask->bitValue($this->_parent->flags,$this->_top->OrderList->getFlagIndex($param));
        }
	}	
	
	public function __set($param,$value)
	{
		$flagIndex = $this->_top->OrderList->getFlagIndex($param);
		
		if ($value==1) {
			$this->_parent->flags = $this->_top->BitMask->setOn($this->_parent->flags,$flagIndex);
		} elseif ($value==0) {
			$this->_parent->flags = $this->_top->BitMask->setOff($this->_parent->flags,$flagIndex);
		}
	}
}


abstract class A_OrderProfile
{
	protected 
		$_top,
		$_parent,
		$_order_id,
		$_profile_data;
		
	public function __construct($top, $parent, $order_id) {
		$this->_top = $top;
		$this->_parent = $parent;
		$this->_order_id = $order_id;
		$this->loader();
	}
	
	abstract protected function loader();
	
	abstract public function __get($param);
	
	abstract public function __set($param,$value);
	
	abstract public function delete();
}

abstract class A_GoodProfile
{
	protected 
		$_top,
		$_parent,
		$_item_id,
		$_profile_data;
		
	public function __construct($top, $parent, $item_id) {
		$this->_top = $top;
		$this->_parent = $parent;
		$this->_item_id = $item_id;
		$this->loader();
	}
	
	abstract protected function loader();
	
	abstract public function __get($param);
	
	abstract public function __set($param,$value);
	
	abstract public function delete();
}

?>